//@author A0088498A
package common;

import hirondelle.date4j.DateTime;
import java.util.Comparator;

/**
 * This class gives the definition of Task <br/>
 */

public class Task implements Comparable<Task> {
	private final String NIL_MESSAGE = "";
	private final String TASK_TUPLE = "%1$s | %2$s | %3$s | %4$s | %5$s | %6$s";
	private final String TUPLE_TIMED = "[%3$s Task]\nName: %1$s \nDescription: %2$s \nFrom: %4$s \nTo: %5$s \nStatus: %6$s\n";
	private final String TUPLE_DEADLINE = "[%3$s Task]\nName: %1$s \nDescription: %2$s \nDue: %4$s \nStatus: %5$s\n";
	private final String TUPLE_FLOATING = "[%3$s Task]\nName: %1$s \nDescription: %2$s \nStatus: %4$s\n";

	private int _id; // Unique number given upon creation (or import)
	private String _name;
	private String _description;
	private TaskType _taskType;
	private DateTime _startingTime;
	private DateTime _endingTime;
	private TaskStatus _status;
	private DateHelper _dh;

	private static int CURRENT_ID = 0;

	/**
	 * Constructor: _id will be assigned with an unique number, status will be
	 * assign undone as default <br/>
	 * 
	 * @param name
	 *            compulsory field for a task
	 * @param type
	 *            deadline, timed, floating
	 * @param description
	 *            can be null
	 * @param startingTime
	 *            null for deadline and floating tasks
	 * @param endingTime
	 *            null for floating task
	 */
	public Task(String name, TaskType type, String description, DateTime startingTime, DateTime endingTime) {
		_id = CURRENT_ID++;
		_name = name;
		setDescription(description);
		_taskType = type;
		_startingTime = startingTime;
		_endingTime = endingTime;
		_status = TaskStatus.undone;
		_dh = new DateHelper();
	}

	/**
	 * Constructor: _id will be assigned with an unique number. <br/>
	 * 
	 * @param name
	 *            compulsory field for a task
	 * @param type
	 *            deadline, timed, floating
	 * @param description
	 *            can be null
	 * @param startingTime
	 *            null for deadline and floating tasks
	 * @param endingTime
	 *            null for floating task
	 */
	public Task(String name, TaskType type, String description, DateTime startingTime, DateTime endingTime, TaskStatus status) {
		_id = CURRENT_ID++;
		_name = name;
		setDescription(description);
		_taskType = type;
		_startingTime = startingTime;
		_endingTime = endingTime;
		_status = status;
		_dh = new DateHelper();
	}

	/**
	 * Check whether the task collide with the other task <br/>
	 * Prerequisite: both are undone or blocked timed Tasks, one of them must
	 * has a status blocked <br/>
	 * Non satisfying the prerequisite will lead to false result <br/>
	 * 
	 * @param task
	 *            the first task
	 * @param otherTask
	 *            the second task
	 * @return true if the prerequisite are met and the first task collides with
	 *         the second task
	 */
	public static boolean checkCollision(Task task, Task otherTask) {
		if (isMetCollisionCheckingPrerequisite(task, otherTask)) {
			// Sort the tasks according to their startingTime
			Task firstTask, laterTask;
			if (task.getStartingTime().compareTo(otherTask.getStartingTime()) > 0) {
				firstTask = otherTask;
				laterTask = task;
			} else {
				firstTask = task;
				laterTask = otherTask;
			}

			// Compare the end time of the first task with the start time of the
			// second
			if (firstTask.getEndingTime().compareTo(laterTask.getStartingTime()) > 0) {
				return true;
			} else {
				return false;
			}
		} else {
			return false;
		}
	}

	/**
	 * Check Task Collision for 2 timed (or block) Task <br/>
	 * Prerequisite: Tasks must be timed Task <br/>
	 * 
	 * @param task
	 * @param otherTask
	 * 
	 * @return true if 2 tasks met the prerequisite and are collided in time,
	 *         otherwise false
	 */
	public static boolean checkTimeCollision(Task task, Task otherTask) {
		if (isMetTimeCollisionCheckingPrerequisite(task, otherTask)) {
			// Sort the tasks according to their startingTime
			Task firstTask, laterTask;
			if (task.getStartingTime().compareTo(otherTask.getStartingTime()) > 0) {
				firstTask = otherTask;
				laterTask = task;
			} else {
				firstTask = task;
				laterTask = otherTask;
			}

			// Compare the end time of the first task with the start time of the
			// second
			if (firstTask.getEndingTime().compareTo(laterTask.getStartingTime()) > 0) {
				return true;
			} else {
				return false;
			}
		} else {
			return false;
		}
	}

	/**
	 * Used for checking Prerequisite for checkCollision <br/>
	 * 
	 * @param task
	 * @param otherTask
	 * @return true when both tasks are timed task, otherwise false
	 */
	private static boolean isMetTimeCollisionCheckingPrerequisite(Task task, Task otherTask) {
		if (task == null || otherTask == null) {
			return false;
		}
		if (task.getTaskType() != TaskType.timed || otherTask.getTaskType() != TaskType.timed) {
			return false;
		}

		return true;
	}

	/**
	 * Used for checking Prerequisite for checkCollision <br/>
	 * 
	 * @param task
	 * @param otherTask
	 * @return true when both are undone or blocked timed Tasks, one of them
	 *         must has a status blocked, otherwise false
	 */
	private static boolean isMetCollisionCheckingPrerequisite(Task task, Task otherTask) {
		if (task == null || otherTask == null) {
			return false;
		}
		if (task.getTaskType() != TaskType.timed || otherTask.getTaskType() != TaskType.timed) {
			return false;
		}
		if (task.getStatus() != TaskStatus.undone && task.getStatus() != TaskStatus.block) {
			return false;
		}
		if (otherTask.getStatus() != TaskStatus.undone && otherTask.getStatus() != TaskStatus.block) {
			return false;
		}
		if (task.getStatus() != TaskStatus.block && otherTask.getStatus() != TaskStatus.block) {
			return false;
		}
		return true;
	}

	/**
	 * @return the Task Id
	 */
	public int getTaskId() {
		return _id;
	}

	/**
	 * @return task name
	 */
	public String getName() {
		return _name;
	}

	/**
	 * Set the new name for the Task
	 * 
	 * @param name
	 *            to be set
	 */
	public void setName(String name) {
		this._name = name;
	}

	/**
	 * Get the task description
	 * 
	 * @return the task description
	 */
	public String getDescription() {
		return _description;
	}

	/**
	 * Set the new description for the Task. If the description is null then the
	 * null place holder will be set instead
	 * 
	 * @param description
	 *            to be set
	 */
	public void setDescription(String description) {
		if (description != null) {
			this._description = description;
		} else {
			_description = NIL_MESSAGE;
		}
	}

	/**
	 * Get the starting time of the Task
	 * 
	 * @return the starting time in DateTime object
	 */
	public DateTime getStartingTime() {
		return _startingTime;
	}

	/**
	 * Set the new starting time of the Task
	 * 
	 * @param startingTime
	 *            new startingTime
	 */
	public void setStartingTime(DateTime startingTime) {
		this._startingTime = startingTime;
	}

	/**
	 * Get the ending time of the Task
	 * 
	 * @return the ending time in DateTime object
	 */
	public DateTime getEndingTime() {
		return _endingTime;
	}

	/**
	 * Set the new ending time of the Task
	 * 
	 * @param endingTime
	 *            new endingTime
	 */
	public void setEndingTime(DateTime endingTime) {
		this._endingTime = endingTime;
	}

	/**
	 * Get the TaskType of the Task
	 * 
	 * @return the TaskType of the Task
	 */
	public TaskType getTaskType() {
		return _taskType;
	}

	/**
	 * Set the new TaskType of the Task
	 * 
	 * @param type
	 *            the new TaskType
	 */
	public void setTaskType(TaskType type) {
		this._taskType = type;
	}

	/**
	 * Get the status of the task
	 * 
	 * @return the current status of the task
	 */
	public TaskStatus getStatus() {
		return _status;
	}

	/**
	 * Set the new status of the task
	 * 
	 * @param status
	 *            new status
	 */
	public void setStatus(TaskStatus status) {
		this._status = status;
	}

	//@author A0097972J
	public String toString() {
		String startFormat = NIL_MESSAGE;
		String endFormat = NIL_MESSAGE;

		if (_startingTime != null) {
			// startFormat = df.format(_startingTime).toString();
			startFormat = _startingTime.toString();
		}
		if (_endingTime != null) {
			// endFormat = df.format(_endingTime).toString();
			endFormat = _endingTime.toString();

		}

		return String.format(TASK_TUPLE, _name, _taskType, _description, startFormat, endFormat, _status);
	}

	// @author A0097972J
	/**
	 * Override toString(), for displaying purposes Changes format from
	 * 2014-03-29 to 29.03.2014
	 * 
	 * @param b
	 */
	public String toString(boolean b) {
		String startFormat = "";
		String endFormat = "";
		String des = "";

		if (_startingTime != null) {
			startFormat = _dh.formatDateTimeToReadableString(_startingTime, false);
		}
		if (_endingTime != null) {
			endFormat = _dh.formatDateTimeToReadableString(_endingTime, true);
		}

		if (!_description.equals(NIL_MESSAGE)) {
			des = _description;
		}

		String typeFormat = _taskType.toString().substring(0, 1).toUpperCase() + _taskType.toString().substring(1);

		if (_taskType.equals(TaskType.floating)) {
			return String.format(TUPLE_FLOATING, _name, des, typeFormat, _status);
		}

		if (_taskType.equals(TaskType.deadline)) {
			return String.format(TUPLE_DEADLINE, _name, des, typeFormat, endFormat, _status);
		}

		return String.format(TUPLE_TIMED, _name, des, typeFormat, startFormat, endFormat, _status);
	}

	/**
	 * Used to detect duplication
	 */
	public int compareTo(Task task) {
		int isEqual = 1;
		if (this.toString().equals(task.toString())) {
			isEqual = 0;
		}
		return isEqual;
	}

	//@author A0088498A
	/**
	 * Changes Date to DateTime d1.lt(d2) check if d2 is less than d1
	 * 
	 */
	public static class ByDate implements Comparator<Task> {
		public int compare(Task t1, Task t2) {
			DateTime d1 = t1._endingTime;
			DateTime d2 = t2._endingTime;

			try {
				if (d1.equals(d2)) {
					return t1.getName().compareTo(t2.getName());
				} else if (d1.lt(d2)) {
					return -1;
				} else {
					return 1;
				}
			} catch (NullPointerException e) {
				if (d1 == null && d2 == null) {
					return t1.getName().compareTo(t2.getName());
				} else if (d1 == null && d2 != null) {
					return 1;
				} else {
					return -1;
				}
			}
		}
	}

	/**
	 * Format the time of the task for display timed: ddmmyy - ddmmyy deadline:
	 * due ddmmyy
	 * 
	 * @return duration
	 */
	public String getDurationString() {
		TaskType type = this._taskType;
		String duration;
		switch (type) {
		case timed:
			duration = "from: " + _dh.formatDateTimeToReadableString(_startingTime, false) + "\nto:   " + _dh.formatDateTimeToReadableString(_endingTime, true);
			break;
		case deadline:
			duration = "due   " + _dh.formatDateTimeToReadableString(_endingTime, true);
			break;
		default:
			duration = "";
		}
		return duration;
	}

	/**
	 * Get the duration of a Task <br/>
	 * 
	 * @return number of minutes of the timed task. -1 if either the starting or
	 *         ending time of the Task is unavailable, or the starting time is
	 *         after the ending time, or the number is too large and get
	 *         overflow
	 */
	public long getDuration() {
		if (_endingTime != null && _startingTime != null && _startingTime.compareTo(_endingTime) <= 0) {
			long duration = (_startingTime.numSecondsFrom(_endingTime)) / 60;
			if (duration >= 0) {
				return (_startingTime.numSecondsFrom(_endingTime)) / 60;
			} else {
				return -1;
			}
		} else {
			return -1;
		}
	}

}
